import math
from tkinter import Tk, Label, filedialog
from PIL import ImageTk, Image
import os
import re
import tkinter as tk
from queue import Queue
from concurrent.futures import ThreadPoolExecutor
import psutil
import gc


class Window:

    def __init__(self, window):
        self.window = window

        # 设置全局颜色方案
        self.window.tk_setPalette(background='#2d2d2d', foreground='#2d2d2d')

        # 窗口名称
        self.window.title("window image")

        # 窗口的大小和位置
        # 大小参数400x400
        # 位置参数+600+600
        # self.window.geometry("400x400+600+600")
        self.window.geometry("1100x800+300+200")

        self.window.config(cursor="arrow")

        # 创建画布，加滚动条
        self.canvas = tk.Canvas(self.window)

        # pack 布局（自动排列）
        self.canvas.pack(side="left", fill="both", expand=True)

        # 创建滚动条
        self.scrollbar = tk.Scrollbar(self.canvas, orient="vertical", command=self.canvas.yview())

        # 滚动条控制画布，解决拖动滚动条内容不变的问题
        self.scrollbar.config(command=self.canvas.yview)

        self.scrollbar.pack(side="right", fill="y")

        self.canvas.configure(yscrollcommand=self.scrollbar.set)

        # 菜单相关 start

        # 创建菜单内容容器
        self.mean_frame = tk.Frame(self.canvas, bg="#2d2d2d")

        # 控制窗口相对于坐标点的位置，默认是中心，设置为nw，就是左上角
        self.canvas.create_window(0, 10, window=self.mean_frame, anchor=tk.NW)

        # width 和 height 的值是字符个数
        # 将按钮放到容器 frame 中，而不是画布 canvas 中
        open_root_button = tk.Button(self.mean_frame, text="open file", command=lambda: self.get_root_dir(), cursor="arrow", width=30, height=2, bd=0, padx=0, pady=0, background="#A9A9A9", foreground="#FFFAFA")

        # 在容器中之内使用 pack 布局（自动排列），否则按钮不显示
        # padx 横轴内边距，pady 纵轴内边距，side 指定组件排列方向
        open_root_button.pack(padx=10, pady=6, side=tk.LEFT)

        # width 和 height 的值是字符个数
        # 将按钮放到容器 frame 中，而不是画布 canvas 中
        close_root_button = tk.Button(self.mean_frame, text="close file", command=lambda: self.close_root_dir(), cursor="arrow", width=30, height=2, bd=0, padx=0, pady=0, background="#A9A9A9", foreground="#FFFAFA")

        # 在容器中之内使用 pack 布局（自动排列），否则按钮不显示
        # padx 横轴内边距，pady 纵轴内边距，side 指定组件排列方向
        close_root_button.pack(padx=5, pady=6, side=tk.LEFT)

        # 菜单相关 end

        # 创建漫画内容容器
        self.comic_frame = tk.Frame(self.canvas, bg="#2d2d2d")

        # 控制窗口相对于坐标点的位置，默认是中心，设置为nw，就是左上角
        self.canvas.create_window(0, 64, window=self.comic_frame, anchor=tk.NW)

        # 创建章节内容容器
        self.chapter_frame = tk.Frame(self.canvas, bg="#2d2d2d")

        # 控制窗口相对于坐标点的位置，默认是中心，设置为nw，就是左上角
        self.canvas.create_window(229, 64, window=self.chapter_frame, anchor=tk.NW)

        # 创建图片内容容器
        self.image_frame = tk.Frame(self.canvas, bg="#2d2d2d")

        # 控制窗口相对于坐标点的位置，默认是中心，设置为nw，就是左上角
        self.canvas.create_window(469, 68, window=self.image_frame, anchor=tk.NW)

        # 隐藏主窗口
        # window.withdraw()

        self.comic_frame.bind("<Configure>", self.update_scroll_region)
        self.chapter_frame.bind("<Configure>", self.update_scroll_region)
        self.image_frame.bind("<Configure>", self.update_scroll_region)

        # 绑定鼠标滚动事件
        self.bind_scroll_events()

        self.image_queue = Queue(maxsize=500)

        # 保存所有PhotoImage对象，解决图片不显示问题
        self.image_references = []

        # 图片线程池
        self.image_executor = ThreadPoolExecutor(max_workers=5)

        # 启动队列监听
        self.window.after(100, self.process_queue)
        self.window.after(100, self.log_memory_usage)

        # 启动Tkinter主事件循环
        # window.mainloop()

    # 当内容变化时更新滚动区域
    def update_scroll_region(self, event):
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

    # 绑定鼠标滚动
    def bind_scroll_events(self):
        # Windows/Linux
        self.canvas.bind_all("<MouseWheel>", self.on_mouse_wheel)
        # MacOS
        self.canvas.bind_all("<Button-4>", self.on_mouse_wheel)
        self.canvas.bind_all("<Button-5>", self.on_mouse_wheel)

    # 处理滚轮滚动
    def on_mouse_wheel(self, event):
        if event.num == 4 or event.delta == 120:  # 向上滚动
            self.canvas.yview_scroll(-1, "units")
        elif event.num == 5 or event.delta == -120:  # 向下滚动
            self.canvas.yview_scroll(1, "units")

    # 获得根目录
    def get_root_dir(self):
        # 打开选中文件夹
        root_dir = filedialog.askdirectory()
        # print("root dir", root_dir)

        if root_dir is not None and root_dir != '':
            self.get_all_comic(root_dir)

    # 关闭根目录
    def close_root_dir(self):
        self.clear_children_element(self.comic_frame)
        self.clear_children_element(self.chapter_frame)
        self.clear_children_element(self.image_frame)

    # 获取所有章节图片
    def get_all_img(self, chapter_dir):
        print("chapter dir", chapter_dir)

        self.clear_children_element(self.image_frame)

        self.image_queue = Queue(maxsize=500)

        # 清除引用图片数组
        self.image_references = []

        # 只有章节为目录时才可能返回图片
        if os.path.isdir(os.path.join(chapter_dir)):
            # 所有图片集合
            image_list = os.listdir(chapter_dir)

            self.load_img_async(image_list, chapter_dir, 0, 0)

    # 异步查询剩余图片，这个方法在不使用队列时使用
    def get_remain_img(self, image_list, chapter_dir, start, end):
        # 显示多张图片需要这个集合
        result_image_list = []

        image_list_length = len(image_list)

        for index in range(image_list_length):
            target_index = index + start

            if target_index >= image_list_length:
                break

            if end != 0:
                if end >= image_list_length:
                    end = image_list_length - 1

                # 为了保证左开右闭
                if target_index >= end:
                    break

            image = image_list[target_index]

            if image.endswith(".jpg") or image.endswith(".png"):
                # 拼接成完整的文件路径
                image_url = chapter_dir + "/" + image
                # print("image url", image_url)

                # 使用Pillow加载图像
                image_open = Image.open(image_url)

                # 获取原始图片的宽度和高度，计算累计图片高度
                original_width, original_height = image_open.size

                # 目标宽度和目标高度
                target_width = 600
                target_height = int((target_width / original_width) * original_height)

                # 重新设置图片的宽高
                tmp_image = image_open.resize((target_width, target_height), resample=Image.LANCZOS)

                # 读取重新设置了宽高的图片并创建ImageTk对象
                resize_image = ImageTk.PhotoImage(tmp_image)

                result_image_list.append(resize_image)

                # 创建Label控件并显示图片，后面三个为0的参数是为了清除外边距，让图片无缝贴合
                label = Label(self.image_frame, image=result_image_list[index], bd=0, padx=0, pady=0)  # bd 消除的边框宽度

                # 关键！！！没有这个图片不显示
                label.image = resize_image

                label.config(width=600)

                # 将Label控件添加到窗口中显示
                # expand​​ 是否分配额外空间， fill 控制组件填充方式，tk.Y 垂直填充
                label.pack(pady=0, ipady=0, expand=True, fill=tk.Y)

                # 防止图片被当垃圾回收
                self.image_references.append(resize_image)

    # 异步加载图片
    def load_img_async(self, image_list, chapter_dir, start, end):
        # 提交线程任务到线程池
        self.image_executor.submit(self.thread_task, image_list, chapter_dir, start, end)

        # 非阻塞关闭线程池（允许未完成任务继续执行）
        # self.image_executor.shutdown(wait=False)

    # 线程方法，将文件路径添加到队列
    def thread_task(self, image_list, chapter_dir, start, end):
        image_list_length = len(image_list)

        for index in range(image_list_length):
            target_index = start + index

            if target_index >= image_list_length:
                break

            if end != 0:
                if end >= image_list_length:
                    end = image_list_length - 1

                if target_index >= end:
                    break

            image = image_list[target_index]

            if image.endswith(".jpg") or image.endswith(".png"):
                # 拼接成完整的文件路径
                image_url = chapter_dir + "/" + image

                # 使用Pillow加载图像
                # 使用 with 语句确保 Image.open 打开的图片文件及时关闭
                with Image.open(image_url) as image_open:
                    # 获取原始图片的宽度和高度，计算累计图片高度
                    original_width, original_height = image_open.size

                    # 目标宽度和目标高度
                    target_width = 600
                    target_height = int((target_width / original_width) * original_height)

                    # 重新设置图片的宽高
                    tmp_image = image_open.resize((target_width, target_height), resample=Image.LANCZOS)

                    # 读取重新设置了宽高的图片并创建ImageTk对象
                    resize_image = ImageTk.PhotoImage(tmp_image)

                    self.image_queue.put(resize_image)

                    self.image_references.append(resize_image)

        # print("当前queue长度", self.image_queue.qsize())

    # 主线程队列中处理图片
    def process_queue(self):
        # print("process_queue thread name：", current_thread().name)
        try:
            # print("queue qsize", self.image_queue.qsize())
            # print("image_references size", len(self.image_references))
            index = 0
            while not self.image_queue.empty():
                index = index + 1

                resize_image = self.image_queue.get(block=False)

                # 创建Label控件并显示图片，后面三个为0的参数是为了清除外边距，让图片无缝贴合
                label = Label(self.image_frame, image=resize_image, bd=0, padx=0, pady=0)  # bd 消除的边框宽度

                # 关键！！！没有这个图片不显示
                label.image = resize_image

                label.config(width=600)

                # 将Label控件添加到窗口中显示
                # expand 是否分配额外空间， fill 控制组件填充方式，tk.Y 垂直填充
                label.pack(pady=0, ipady=0, fill=tk.Y, expand=True)

                gc.collect()  # 手动触发垃圾回收

                # 标记任务完成
                # self.image_queue.task_done()
        except Exception as e:
            print("队列出现异常！", e)

        # 每100ms检查一次队列
        self.window.after(2000, self.process_queue)

    # 获得所有章节名称
    def get_all_chapter(self, comic_dir):
        print("comic dir", comic_dir)

        self.clear_children_element(self.chapter_frame)

        # 获取当前目录的所以子目录，不包括子目录中的目录
        file_list = os.listdir(comic_dir)
        chapter_list = []
        for file in file_list:
            # 只有目录才返回
            if os.path.isdir(os.path.join(comic_dir, file)):
                # print("dir name", file)
                chapter_list.append(file)

        chapter_list_length = len(chapter_list)

        # “(?P<num>\d+)”将章节的名称命名为“num”
        name_pattern = r'\第(?P<num>\d+)(\话|\話)'
        # last_name_pattern = r'最终(話|话)'  #  这个正则表达式匹配不到
        last_name_pattern = r'(最終話)|(最終话)'

        # 冒泡排序计算章节顺序
        for i in range(chapter_list_length):
            for j in range(chapter_list_length - i - 1):
                j_chapter_name = chapter_list[j]
                j_1_chapter_name = chapter_list[j + 1]

                j_chapter_num_str = re.match(name_pattern, j_chapter_name)
                j_1_chapter_num_str = re.match(name_pattern, j_1_chapter_name)

                if j_chapter_num_str is None:
                    if re.match(last_name_pattern, j_chapter_name):
                        chapter_list[j], chapter_list[chapter_list_length - 1] = chapter_list[chapter_list_length - 1], chapter_list[j]  # 交换相邻的元素
                    continue
                else:
                    j_chapter_num = int(j_chapter_num_str.group("num"))

                if j_1_chapter_num_str is None:
                    if re.match(last_name_pattern, j_1_chapter_name):
                        chapter_list[j + 1], chapter_list[chapter_list_length - 1] = chapter_list[chapter_list_length - 1], chapter_list[j + 1]  # 交换相邻的元素
                    continue
                else:
                    j_1_chapter_num = int(j_1_chapter_num_str.group("num"))

                if j_chapter_num > j_1_chapter_num:
                    chapter_list[j], chapter_list[j + 1] = chapter_list[j + 1], chapter_list[j]  # 交换相邻的元素

        # 重新起一个循环，不然章节显示还是原来的顺序
        for i in range(chapter_list_length):
            # width 和 height 的值是字符个数
            # 将按钮放到容器 frame 中，而不是画布 canvas 中
            chapter_button = tk.Button(self.chapter_frame, text=chapter_list[i], command=lambda x=i: self.get_all_img(comic_dir + "/" + chapter_list[x]), cursor="arrow", width=30, height=2, bd=0, padx=0, pady=0, background="#A9A9A9", foreground="#FFFAFA")

            # 在容器中之内使用 pack 布局（自动排列），否则按钮不显示
            # padx 横轴内边距，pady 纵轴内边距，side 指定组件排列方向
            chapter_button.pack(padx=10, pady=5, side=tk.TOP)

        # # 显示图片时需要紧跟着，不然图片显示不出来
        # self.window.mainloop()

        return chapter_list

    # 清除 frame 的子元素
    def clear_children_element(self, frame):
        for element in frame.winfo_children():
            element.destroy()

    # 获取所有漫画
    def get_all_comic(self, root_dir):
        print("root dir", root_dir)

        # 只有为目录才有可能返回记录
        if os.path.isdir(os.path.join(root_dir)):
            # 获取当前目录的所有子目录，不包括子目录中的目录
            comic_list = os.listdir(root_dir)
            for index, comic in enumerate(comic_list):

                # 只有目录才返回
                if os.path.isdir(os.path.join(root_dir, comic)):
                    # width 和 height 的值是字符个数
                    comic_button = tk.Button(self.comic_frame, text=comic, command=lambda x=index: self.get_all_chapter(root_dir + "/" + comic_list[x]), cursor="arrow", width=30, height=2, bd=0, padx=0, pady=0, background="#A9A9A9", foreground="#FFFAFA")

                    # 在容器中之内使用 pack 布局（自动排列），否则按钮不显示
                    # padx 横轴内边距，pady 纵轴内边距，side 指定组件排列方向
                    comic_button.pack(padx=10, pady=5, side=tk.TOP)

        return comic_list

    # 获得所有文件
    def get_all_file(self, root_dir):
        for file_name in os.listdir(root_dir):
            if file_name.endswith(".jpg") or file_name.endswith(".png"):
                # 拼接成完整的文件路径
                file_url = root_dir + "/" + file_name
                print("file_url", file_url)

                image_open = Image.open(file_url)

                # 读取图片并创建ImageTk对象
                image = ImageTk.PhotoImage(image=image_open)

                # 创建Label控件并显示图片
                label = Label(image=image)
                label.config(width=400, height=10)

                # 将Label控件添加到窗口中显示
                label.pack(padx=30, side=tk.LEFT)

    def test(self):
        chapter_dir = "F:/文档/漫画/放弃/她才不是我姊姊/第1話"

        # 只有章节为目录时才可能返回图片
        if os.path.isdir(os.path.join(chapter_dir)):
            # 所有图片集合
            image_list = os.listdir(chapter_dir)

            self.load_img_async(image_list, chapter_dir, 0, 0)
            # self.get_remain_img(image_list, chapter_dir, 0, 0)

    # 监听内存使用
    def log_memory_usage(self):
        process = psutil.Process()
        mem = process.memory_info().rss / 1024 ** 2  # MB
        print(f"当前内存占用: {mem:.2f} MB")
        self.window.after(5000, self.log_memory_usage)  # 每5秒记录一次


if __name__ == "__main__":
    window = Tk()

    Window(window)

    window.mainloop()
